Vue 2 vs Vue 3 的响应式实现
| 版本 | 核心 API | 限制 |
|---|---|---|
| Vue 2 | Object.defineProperty | 无法检测属性新增/删除;需要 Vue.set() |
| Vue 3 | Proxy + Reflect | 不支持 IE(微软已放弃 IE) |
Vue 3 放弃 IE 支持后,可以直接使用 ES6 的 Proxy,解决了 Vue 2 的诸多限制。
Proxy 基础
Proxy 用于创建一个对象的代理,拦截并自定义基本操作:
const product = { price: 10, quantity: 2 }
const productProxy = new Proxy(product, {
get(target, key, receiver) {
// 拦截属性读取
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
// 拦截属性设置
return Reflect.set(target, key, value, receiver)
}
})
javascript
三种访问对象属性的方式
// 1. 点号访问
product.price
// 2. 方括号访问
product['price']
// 3. Reflect 访问
Reflect.get(product, 'price')
javascript
为什么用 Reflect
在 Proxy 的 get 处理器中使用 Reflect.get 而非直接 target[key],原因在于 receiver 参数:
get(target, key, receiver) {
// receiver 指向 Proxy 对象本身
// Reflect.get 保证了 this 指向 proxy 而非原始 target
const result = Reflect.get(target, key, receiver)
track(target, key) // 追踪依赖
return result
}
javascript
receiver 确保了当对象属性通过 getter 访问时,this 指向 Proxy 对象而非原始对象,保证了响应式行为的一致性。
reactive 函数实现
将 Proxy + track/trigger 封装为 reactive 函数:
function reactive(target) {
const handler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
track(target, key)
return result
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (oldValue !== result) {
trigger(target, key)
}
return result
}
}
return new Proxy(target, handler)
}
javascript
使用示例
const product = reactive({ price: 10, quantity: 2 })
let total = 0
// 注册副作用
effect(() => {
total = product.price * product.quantity
})
console.log(total) // 20
product.price = 20 // 自动触发 effect
console.log(total) // 40
javascript
activeEffect 与 effect 函数
为了只在实际使用响应式属性时才追踪副作用,引入全局变量 activeEffect:
let activeEffect = null
function effect(fn) {
activeEffect = fn
fn() // 执行时触发 get → track
activeEffect = null // 执行完毕后清空
}
javascript
这样,console.log(product.price) 等普通读取操作不会触发不必要的依赖追踪。
Vue 3 响应式的优势
通过 Proxy 实现的响应式系统,天然支持动态添加属性:
const product = reactive({ price: 10 })
// Vue 2 中需要 Vue.set(product, 'name', 'iPhone')
// Vue 3 中直接赋值即可
product.name = 'iPhone' // 自动响应
javascript
这是 Vue 3 采用 Proxy 的核心收益之一:无需 Vue.set(),新增属性自动具备响应式能力。
↑